package tom.spring.cloud.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 基于编码实现, profiles: default
 * http://localhost:8090/actuator/gateway/routes
 * spring-cloud-gateway网关
 * @author ZHUFEIFEI
 */
@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
                classes = {Gateway02Application.class, Gateway03Application.class})
})
public class Gateway01Application {

    public static void main(String[] args) {
        SpringApplication.run(Gateway01Application.class, args);
    }

    @RestController
    class GatewayController {
        @RequestMapping("/hystrixfallback")
        public String hystrixfallback() {
            return "This is a fallback";
        }
    }

    /**
     * java编程形式的路由配置，可以使用yml配置文件形式. <br>
     * <p>
     * 关于测试：FireFox 访问F12, 编辑重发，不同的host路由使用不同的route。
     * {@link org.springframework.web.server.handler.FilteringWebHandler}
     * {@link org.springframework.web.server.adapter.HttpWebHandlerAdapter}
     * {@link org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration}
     *  RedisRateLimiter 基于令牌桶限速 {@link org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory}
     *  {@link RedisRateLimiter} see request_rate_limiter.lua 具体限速逻辑
     *  </p>
     *  <p>
     *      RateLimiter(No Configuration found for route limit_rate or defaultFilters) 问题
     *      {@link org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator}
     *      RouteLocator有一个Publisher发布{@link org.springframework.cloud.gateway.event.FilterArgsEvent}
     *      ，里面包含了Filter的配置参数，里面就可以设置RateLimiter的令牌桶参数。
     *      该事件由{@link org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter} 监听
     *      配置式时候配置属性可以通过事件发布。
     *      编程形式采用头部传递参数RateLimiter参数
     *  </p>
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder, RedisRateLimiter redisRateLimiter) {
        //设置限流配置，默认的bean没有设置.
        redisRateLimiter.getConfig().put("limit_rate",
                //设置令牌放入速度没秒3个，最大5个请求，针对每个用户起效，而不是所有请求
                new RedisRateLimiter.Config().setReplenishRate(3)
                .setBurstCapacity(5)
        );
        return builder.routes()
                //路径匹配路由, 下面的方法path() host() hystrix() filter分别对应RoutePredicateFactory的实现和GatewayFilterFactory的实现
                .route("path_route", r -> r.path("/hello").uri("http://localhost:8021"))
                //header中的host匹配路由，请求头中的HOST:BZD22838xin路由到本机8021端口
                .route("host_route", r -> r.host("a.b.c").uri("http://localhost:8021"))
                //请求头HOST:xxx.rewrite.org匹配/foo/xxx路径并转发到本机8021端口
                .route("rewrite_route", r -> r.host("*.rewrite.org")
                        .filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
                        .uri("http://localhost:8021")
                )
                //请求头HOST:xxx.hystrix.org, 添加过滤器并设置hystrix熔断命令,需要引入netflix-hystrix
                .route("hystrix_route", r -> r.host("*.hystrix.org")
                        .filters(f -> f.hystrix(c -> c.setName("slowcmd")))
                        .uri("http://localhost:8021")
                )
                //请求头*.hystrixfallback.org, 设置hystrix命令熔断将跳转到指定地址
                .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
                        .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
                        .uri("http://localhost:8021")
                )
                //请求头host为*.limit.org，请求限速过滤器，使用redis限速实现, 需引入starter-data-redis
                //RateLimiter需要认证信息默认采用PrincipalNameKeyResolver从exchange中提取登陆信息
                // ，所以还需要引入starter-security，可以自己实现keyResolver
                //之所以需要使用security是因为RedisRateLimiter采用的是针对用户级别的限速，
                //也就是针对每个用户限速，所以使用用户名作为限速逻辑key，保存到redis中做限速逻辑计算
                .route("limit_rate", r -> r.host("*.limit.org").and().path("/limit/**")
                        .filters(f ->
                                f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter))
                        )
                        .uri("http://localhost:8021")
                )
                .build();
    }

    /**
     *  配合ServerWebExchange实现请求权限过滤.<br>
     *  <h3>关于/limit/**的验证</h3> <br>
     *  <p>
     *  请求发来会经过{@link org.springframework.security.web.server.authentication.AuthenticationWebFilter}
     *  在该Filter中使用{@link org.springframework.security.web.server.authentication.ServerAuthenticationConverter}
     *  该converter从请求中提取认证信息，下面配置的httpBasic {@link ServerHttpSecurity},还有formLogin认证登陆形式。
     *  这两种形式的认证方式对应两个Converter，认证信息的提取默认有两个实现{@link org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter}
     *  {@link org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter} <br>
     *  formLogin的认证信息读取源码：{@link org.springframework.web.server.adapter.DefaultServerWebExchange} 的构造函数中initFormData，
     *  通过{@link org.springframework.http.codec.FormHttpMessageReader}, 注意httpBasic形式的传递方式为
     *  Header中 Authentication: Basic base64(user:pass)形式，具体参照Basic的Converter提取源码
     *  </p>
     * @param http
     * @return
     */
    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
        return http.httpBasic().and()
                //Cross-site request forgeries 跨站伪造攻击
                //禁用 跨站伪造攻击 防护
                .csrf().disable()
                // /limit/**的需要认证
                .authorizeExchange()
                .pathMatchers("/limit/**").authenticated()
                //其他请求全部允许
                .anyExchange().permitAll()
                .and()
                .build();
    }


    /**
     *  代码方式自定义UserDetailsService用于实现用户认证，如果不自定义在ReactiveUserDetailsServiceAutoConfiguration
     *  中会有定义使用SecurityProperties配置的用户和密码(yml中配置固定用户密码)，该种形式只用于测试。
     *  生产环境参照MapReactiveUserDetailsService实现自己的用户信息加载服务
     *  spring-security引入说明README.MD
     * {@link org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration}
     * {@link org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfiguration}
     * {@link org.springframework.security.web.server.authentication.AuthenticationWebFilter}
     * {@link org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter}
     * @return userDetailsService
     */
//    @Bean
//    public MapReactiveUserDetailsService reactiveUserDetailsService() {
//        UserDetails user = User.withUsername("user").password("password").roles("USER").build();
//        return new MapReactiveUserDetailsService(user);
//    }

}
